package com.fram.common.util.wxpay;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.fram.bean.admin.ordermain.response.OrderResponse;
import com.fram.bean.api.order.response.OrderDetailResponse;
import com.fram.common.canstant.ErrCode;
import com.fram.common.exception.ProAffairException;
import com.fram.common.exception.ProRunTimeException;
import com.fram.common.util.PublicMethod;
import com.fram.common.util.date.DateUtils;
import com.fram.common.util.http.HttpClientTools;
import com.fram.entity.OrderRefund;
import com.fram.entity.SysPayment;
import com.fram.entity.UserLogin;

public class WXPayUtils {

	private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

	private static final Random RANDOM = new SecureRandom();

	private static Logger LOG = LoggerFactory.getLogger(WXPayUtils.class);

	/**
	 * XML格式字符串转换为Map
	 *
	 * @param strXML
	 *            XML字符串
	 * @return XML数据转换后的Map
	 * @throws Exception
	 */
	public static Map<String, String> xmlToMap(String strXML) throws Exception {
		try {
			Map<String, String> data = new HashMap<String, String>();
			DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
			InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
			org.w3c.dom.Document doc = documentBuilder.parse(stream);
			doc.getDocumentElement().normalize();
			NodeList nodeList = doc.getDocumentElement().getChildNodes();
			for (int idx = 0; idx < nodeList.getLength(); ++idx) {
				Node node = nodeList.item(idx);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					org.w3c.dom.Element element = (org.w3c.dom.Element) node;
					data.put(element.getNodeName(), element.getTextContent());
				}
			}
			try {
				stream.close();
			} catch (Exception ex) {
				// do nothing
			}
			return data;
		} catch (Exception ex) {
			WXPayUtils.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
			throw ex;
		}

	}

	/**
	 * 将Map转换为XML格式的字符串
	 *
	 * @param data
	 *            Map类型数据
	 * @return XML格式的字符串
	 * @throws Exception
	 */
	public static String mapToXml(Map<String, String> data) throws Exception {
		org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
		org.w3c.dom.Element root = document.createElement("xml");
		document.appendChild(root);
		for (String key : data.keySet()) {
			String value = data.get(key);
			if (value == null) {
				value = "";
			}
			value = value.trim();
			org.w3c.dom.Element filed = document.createElement(key);
			filed.appendChild(document.createTextNode(value));
			root.appendChild(filed);
		}
		TransformerFactory tf = TransformerFactory.newInstance();
		Transformer transformer = tf.newTransformer();
		DOMSource source = new DOMSource(document);
		transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		StringWriter writer = new StringWriter();
		StreamResult result = new StreamResult(writer);
		transformer.transform(source, result);
		String output = writer.getBuffer().toString(); // .replaceAll("\n|\r", "");
		try {
			writer.close();
		} catch (Exception ex) {
		}
		return output;
	}

	/**
	 * 生成带有 sign 的 XML 格式字符串
	 *
	 * @param data
	 *            Map类型数据
	 * @param key
	 *            API密钥
	 * @return 含有sign字段的XML
	 */
	public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
		return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
	}

	/**
	 * 生成带有 sign 的 XML 格式字符串
	 *
	 * @param data
	 *            Map类型数据
	 * @param key
	 *            API密钥
	 * @param signType
	 *            签名类型
	 * @return 含有sign字段的XML
	 */
	public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
		String sign = generateSignature(data, key, signType);
		data.put(WXPayConstants.FIELD_SIGN, sign);
		return mapToXml(data);
	}

	/**
	 * 判断签名是否正确
	 *
	 * @param xmlStr
	 *            XML格式数据
	 * @param key
	 *            API密钥
	 * @return 签名是否正确
	 * @throws Exception
	 */
	public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
		Map<String, String> data = xmlToMap(xmlStr);
		if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
			return false;
		}
		String sign = data.get(WXPayConstants.FIELD_SIGN);
		return generateSignature(data, key).equals(sign);
	}

	/**
	 * 判断签名是否正确，必须包含sign字段，否则返回false。使用MD5签名。
	 *
	 * @param data
	 *            Map类型数据
	 * @param key
	 *            API密钥
	 * @return 签名是否正确
	 * @throws Exception
	 */
	public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
		return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
	}

	/**
	 * 判断签名是否正确，必须包含sign字段，否则返回false。
	 *
	 * @param data
	 *            Map类型数据
	 * @param key
	 *            API密钥
	 * @param signType
	 *            签名方式
	 * @return 签名是否正确
	 * @throws Exception
	 */
	public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
		if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
			return false;
		}
		String sign = data.get(WXPayConstants.FIELD_SIGN);
		return generateSignature(data, key, signType).equals(sign);
	}

	/**
	 * 生成签名
	 *
	 * @param data
	 *            待签名数据
	 * @param key
	 *            API密钥
	 * @return 签名
	 */
	public static String generateSignature(final Map<String, String> data, String key) throws Exception {
		return generateSignature(data, key, WXPayConstants.SignType.MD5);
	}

	/**
	 * 生成签名. 注意，若含有sign_type字段，必须和signType参数保持一致。
	 *
	 * @param data
	 *            待签名数据
	 * @param key
	 *            API密钥
	 * @param signType
	 *            签名方式
	 * @return 签名
	 */
	public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
		Set<String> keySet = data.keySet();
		String[] keyArray = keySet.toArray(new String[keySet.size()]);
		Arrays.sort(keyArray);
		StringBuilder sb = new StringBuilder();
		for (String k : keyArray) {
			if (k.equals(WXPayConstants.FIELD_SIGN)) {
				continue;
			}
			if (data.get(k).trim().length() > 0) // 参数值为空，则不参与签名
				sb.append(k).append("=").append(data.get(k).trim()).append("&");
		}
		sb.append("key=").append(key);
		if (WXPayConstants.SignType.MD5.equals(signType)) {
			return MD5(sb.toString()).toUpperCase();
		} else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
			return HMACSHA256(sb.toString(), key);
		} else {
			throw new Exception(String.format("Invalid sign_type: %s", signType));
		}
	}

	/**
	 * 获取随机字符串 Nonce Str
	 *
	 * @return String 随机字符串
	 */
	public static String generateNonceStr() {
		char[] nonceChars = new char[32];
		for (int index = 0; index < nonceChars.length; ++index) {
			nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
		}
		return new String(nonceChars);
	}

	/**
	 * 生成 MD5
	 *
	 * @param data
	 *            待处理数据
	 * @return MD5结果
	 */
	public static String MD5(String data) throws Exception {
		MessageDigest md = MessageDigest.getInstance("MD5");
		byte[] array = md.digest(data.getBytes("UTF-8"));
		StringBuilder sb = new StringBuilder();
		for (byte item : array) {
			sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
		}
		return sb.toString().toUpperCase();
	}

	/**
	 * 生成 HMACSHA256
	 * 
	 * @param data
	 *            待处理数据
	 * @param key
	 *            密钥
	 * @return 加密结果
	 * @throws Exception
	 */
	public static String HMACSHA256(String data, String key) throws Exception {
		Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
		SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
		sha256_HMAC.init(secret_key);
		byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
		StringBuilder sb = new StringBuilder();
		for (byte item : array) {
			sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
		}
		return sb.toString().toUpperCase();
	}

	/**
	 * 日志
	 * 
	 * @return
	 */
	public static Logger getLogger() {
		Logger logger = LoggerFactory.getLogger("wxpay java sdk");
		return logger;
	}

	/**
	 * 获取当前时间戳，单位秒
	 * 
	 * @return
	 */
	public static long getCurrentTimestamp() {
		return System.currentTimeMillis() / 1000;
	}

	/**
	 * 获取当前时间戳，单位毫秒
	 * 
	 * @return
	 */
	public static long getCurrentTimestampMs() {
		return System.currentTimeMillis();
	}

	/** 小程序支付 **/
	public static Map<String, String> xcxPay(OrderDetailResponse detail, SysPayment payment, UserLogin user) throws ProAffairException {
		try {
			MyWxPayConfig config = new MyWxPayConfig();
			config.setAppId(payment.getPayAppid());
			config.setMchId(payment.getPayBossAccount());
			config.setKey(payment.getPayAppKey());
			WXPay wxpay = new WXPay(config);

			Map<String, String> data = new HashMap<String, String>();
			data.put("body", detail.getGoodsList().get(0).getName());
			data.put("out_trade_no", detail.getPayNumber());
			data.put("fee_type", "CNY");
			data.put("total_fee", ((int) (detail.getActualPrice().doubleValue() * 100)) + "");
			data.put("spbill_create_ip", PublicMethod.getIp(PublicMethod.getRequest()));
			data.put("notify_url", payment.getPayNotifyUrl());
			data.put("time_start", DateUtils.DF_CLOSED.get().format(detail.getCreateTime()));// 订单生成时间
			data.put("time_expire", DateUtils.DF_CLOSED.get().format(detail.getInvalidTime()));// 过期时间
			data.put("trade_type", "JSAPI"); // 此处指定为小程序付款
			// 设置请求参数(openid)(在接口文档中 该参数 是否必填项 但是一定要注意 如果交易类型设置成'JSAPI'则必须传入openid)
			data.put("openid", user.getOpenid());
			data.put("appid", payment.getPayAppid());
			String sign = WXPayUtils.generateSignature(data, config.getKey());
			data.put("sign", sign);

			Map<String, String> resp = wxpay.unifiedOrder(data);
			getLogger().info("-支付-微信统一下单响应结果【{}】", resp);
			if ("SUCCESS".equals(resp.get("return_code")) && !StringUtils.isEmpty(resp.get("prepay_id"))) {
				// 再次签名
				/**
				 * 重要的事情说三遍 小程序支付 所有的字段必须大写 驼峰模式 严格按照小程序支付文档 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=3#
				 */
				Map<String, String> reData = new HashMap<>();
				reData.put("appId", config.getAppID());
				reData.put("nonceStr", resp.get("nonce_str"));
				String newPackage = "prepay_id=" + resp.get("prepay_id");
				reData.put("package", newPackage);
				reData.put("signType", WXPayConstants.MD5);
				reData.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));

				String newSign = WXPayUtils.generateSignature(reData, config.getKey());
				reData.put("paySign", newSign);
				reData.put("prepay_id", resp.get("prepay_id"));
				return reData;
			} else {
				throw new ProRunTimeException(ErrCode.FAIL.getReturnCode(), resp.get("err_code_des"));
			}
		} catch (ProAffairException e) {
			throw new ProRunTimeException(e.getErrorCode(), e.getErrorMsg());
		} catch (Exception e) {
			throw new ProRunTimeException(ErrCode.FAIL.getReturnCode(), "生成微信支付失败");
		}
	}

	/**
	 * @作者：陳曉鬆 @时间：2020年5月15日 下午3:27:20 @版本：V1.0 @description：-> 公众号支付
	 */
	public static Map<String, String> publicNumberPay(OrderDetailResponse detail, SysPayment payment, UserLogin user) throws ProAffairException {
		try {
			Map<String, String> reqData = new HashMap<String, String>();
			reqData.put("appid", payment.getPayAppid());
			reqData.put("mch_id", payment.getPayBossAccount());
			reqData.put("nonce_str", PublicMethod.getUUIDUpperCase());
			reqData.put("sign_type", WXPayConstants.MD5);
			reqData.put("notify_url", payment.getPayNotifyUrl());
			reqData.put("spbill_create_ip", detail.getIp());
			reqData.put("body", detail.getGoodsList().get(0).getName());
			reqData.put("out_trade_no", detail.getPayNumber());
			reqData.put("total_fee", ((int) (detail.getActualPrice().doubleValue() * 100)) + "");
			reqData.put("trade_type", "JSAPI");
			reqData.put("openid", user.getOpenid());
			reqData.put("time_start", DateUtils.DF_CLOSED.get().format(detail.getCreateTime()));// 订单生成时间
			reqData.put("time_expire", DateUtils.DF_CLOSED.get().format(detail.getInvalidTime()));// 过期时间
			String sign = WXPayUtils.generateSignature(reqData, payment.getPayAppKey());
			reqData.put("sign", sign);
			String xml = null;
			Map<String, String> responseMap = null;
			try {
				xml = WXPayUtils.mapToXml(reqData);
				String response = HttpClientTools.httpsRequestXml("https://" + WXPayConstants.DOMAIN_API + WXPayConstants.UNIFIEDORDER_URL_SUFFIX, "POST", xml);
				getLogger().info("-支付-微信统一下单接口返回信息：{}", response);
				responseMap = WXPayUtils.xmlToMap(response);
			} catch (Exception e) {
				e.printStackTrace();
				throw new ProRunTimeException("支付参数转换xml异常或返回异常");
			}
			if ("SUCCESS".equals(responseMap.get("result_code"))) {
				Map<String, String> reData = new HashMap<>();
				reData.put("appId", payment.getPayAppid());
				reData.put("nonceStr", responseMap.get("nonce_str"));
				String newPackage = "prepay_id=" + responseMap.get("prepay_id");
				reData.put("package", newPackage);
				reData.put("signType", WXPayConstants.MD5);
				reData.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));

				String newSign = WXPayUtils.generateSignature(reData, payment.getPayAppKey());
				reData.put("paySign", newSign);
				reData.put("prepay_id", responseMap.get("prepay_id"));
				return reData;
			} else {
				throw new ProRunTimeException(ErrCode.FAIL.getReturnCode(),
						responseMap.get("err_code_des") == null ? responseMap.get("return_msg") : responseMap.get("err_code_des"));
			}
		} catch (ProAffairException e) {
			throw new ProRunTimeException(e.getErrorCode(), e.getErrorMsg());
		} catch (Exception e) {
			throw new ProRunTimeException(ErrCode.FAIL.getReturnCode(), "生成微信支付失败");
		}
	}

	/**
	 * @作者：陳曉鬆 @时间：2020年5月15日 下午3:27:20 @版本：V1.0 @description：-> APP支付
	 */
	public static Map<String, String> appPay(OrderDetailResponse detail, SysPayment payment) throws ProAffairException {
		try {
			Map<String, String> reqData = new HashMap<String, String>();
			reqData.put("appid", payment.getPayAppid());
			reqData.put("mch_id", payment.getPayBossAccount());
			reqData.put("nonce_str", PublicMethod.getUUIDUpperCase());
			reqData.put("sign_type", WXPayConstants.MD5);
			reqData.put("notify_url", payment.getPayNotifyUrl());
			reqData.put("spbill_create_ip", detail.getIp());
			reqData.put("body", payment.getPayPublicKey() + "-" + detail.getGoodsList().get(0).getName());
			reqData.put("out_trade_no", detail.getPayNumber());
			reqData.put("total_fee", ((int) (detail.getActualPrice().doubleValue() * 100)) + "");
			reqData.put("trade_type", "APP");
			reqData.put("time_start", DateUtils.DF_CLOSED.get().format(detail.getCreateTime()));// 订单生成时间
			reqData.put("time_expire", DateUtils.DF_CLOSED.get().format(detail.getInvalidTime()));// 过期时间
			String sign = WXPayUtils.generateSignature(reqData, payment.getPayAppKey());
			reqData.put("sign", sign);
			String xml = null;
			Map<String, String> responseMap = null;
			try {
				xml = WXPayUtils.mapToXml(reqData);
				String response = HttpClientTools.httpsRequestXml("https://" + WXPayConstants.DOMAIN_API + WXPayConstants.UNIFIEDORDER_URL_SUFFIX, "POST", xml);
				getLogger().info("-支付-微信统一下单接口返回信息：{}", response);
				responseMap = WXPayUtils.xmlToMap(response);
			} catch (Exception e) {
				e.printStackTrace();
				throw new ProRunTimeException("支付参数转换xml异常或返回异常");
			}
			if ("SUCCESS".equals(responseMap.get("result_code"))) {
				Map<String, String> reData = new HashMap<>();
				reData.put("appid", payment.getPayAppid());
				reData.put("noncestr", responseMap.get("nonce_str"));
				reData.put("package", "Sign=WXPay");
				reData.put("partnerid", responseMap.get("mch_id"));
				reData.put("prepayid", responseMap.get("prepay_id"));
				reData.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
				String newSign = WXPayUtils.generateSignature(reData, payment.getPayAppKey());
				reData.put("sign", newSign);
				reData.put("trade_type", responseMap.get("trade_type"));// 获取预支付交易回话标志
				return reData;
			} else {
				throw new ProRunTimeException(ErrCode.FAIL.getReturnCode(),
						responseMap.get("err_code_des") == null ? responseMap.get("return_msg") : responseMap.get("err_code_des"));
			}
		} catch (ProAffairException e) {
			throw new ProRunTimeException(e.getErrorCode(), e.getErrorMsg());
		} catch (Exception e) {
			throw new ProRunTimeException(ErrCode.FAIL.getReturnCode(), "生成微信支付失败");
		}
	}

	/**
	 * @作者：陳曉鬆 @时间：2020年5月15日 下午3:27:20 @版本：V1.0 @description：->
	 */
	public static Map<String, String> h5Pay(OrderDetailResponse detail, SysPayment payment) throws ProAffairException {
		try {
			Map<String, String> reqData = new HashMap<String, String>();
			reqData.put("appid", payment.getPayAppid());
			reqData.put("mch_id", payment.getPayBossAccount());
			reqData.put("nonce_str", PublicMethod.getUUIDUpperCase());
			reqData.put("sign_type", WXPayConstants.MD5);
			reqData.put("notify_url", payment.getPayNotifyUrl());
			reqData.put("spbill_create_ip", detail.getIp());
			reqData.put("body", detail.getGoodsList().get(0).getName());
			reqData.put("out_trade_no", detail.getPayNumber());
			reqData.put("total_fee", ((int) (detail.getActualPrice().doubleValue() * 100)) + "");
			reqData.put("trade_type", "MWEB");
			reqData.put("time_start", DateUtils.DF_CLOSED.get().format(detail.getCreateTime()));// 订单生成时间
			reqData.put("time_expire", DateUtils.DF_CLOSED.get().format(detail.getInvalidTime()));// 过期时间
			String sign = WXPayUtils.generateSignature(reqData, payment.getPayAppKey());
			reqData.put("sign", sign);
			String xml = null;
			Map<String, String> responseMap = null;
			try {
				xml = WXPayUtils.mapToXml(reqData);
				String response = HttpClientTools.httpsRequestXml("https://" + WXPayConstants.DOMAIN_API + WXPayConstants.UNIFIEDORDER_URL_SUFFIX, "POST", xml);
				getLogger().info("-支付-微信统一下单接口返回信息：{}", response);
				responseMap = WXPayUtils.xmlToMap(response);
			} catch (Exception e) {
				e.printStackTrace();
				throw new ProRunTimeException("支付参数转换xml异常或返回异常");
			}
			if ("SUCCESS".equals(responseMap.get("result_code"))) {
				return responseMap;
			} else {
				throw new ProRunTimeException(ErrCode.FAIL.getReturnCode(),
						responseMap.get("err_code_des") == null ? responseMap.get("return_msg") : responseMap.get("err_code_des"));
			}
		} catch (ProAffairException e) {
			throw new ProRunTimeException(e.getErrorCode(), e.getErrorMsg());
		} catch (Exception e) {
			throw new ProRunTimeException(ErrCode.FAIL.getReturnCode(), "生成微信支付失败");
		}
	}

	/**
	 * @作者：陳曉鬆 @时间：2020年5月20日 上午9:43:31 @版本：V1.0 @description：-> 微信退款
	 */
	public static Map<String, String> wechatRefund(OrderResponse detail, OrderRefund refund, SysPayment payment) throws ProAffairException {
		if (StringUtils.isEmpty(payment.getPayAppid())) {
			throw new ProRunTimeException("开发者appId为空");
		}
		if (StringUtils.isEmpty(payment.getPayBossAccount())) {
			throw new ProRunTimeException("商户编号mchId为空");
		}
		if (StringUtils.isEmpty(payment.getPayAppKey())) {
			throw new ProRunTimeException("开发者密码appKey为空");
		}
		if (StringUtils.isEmpty(detail.getPayNumber())) {
			throw new ProRunTimeException("订单编号为空");
		}
		if (StringUtils.isEmpty(refund.getRefundNumber())) {
			throw new ProRunTimeException("退款单号为空");
		}
		if (detail.getActualPrice() == null) {
			throw new ProRunTimeException("订单总金额为空");
		}
		if (refund.getRefundPrice() == null) {
			throw new ProRunTimeException("退款金额为空");
		}
		Map<String, String> reqData = new HashMap<String, String>();
		reqData.put("appid", payment.getPayAppid());
		reqData.put("mch_id", payment.getPayBossAccount());
		reqData.put("nonce_str", PublicMethod.getUUIDUpperCase());
		reqData.put("sign_type", WXPayConstants.MD5);
		reqData.put("out_trade_no", detail.getPayNumber());
		reqData.put("transaction_id", detail.getPayBillNumber());
		reqData.put("out_refund_no", refund.getRefundNumber());
		reqData.put("total_fee", ((int) (detail.getActualPrice().doubleValue() * 100)) + "");
		reqData.put("refund_fee", ((int) (refund.getRefundPrice().doubleValue() * 100)) + "");
		reqData.put("refund_desc", StringUtils.isEmpty(refund.getRemark()) ? "正常退款" : refund.getRemark());
		reqData.put("notify_url", payment.getPayNotifyUrl());
		// REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款（默认使用未结算资金退款）
		// REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款
		reqData.put("refund_account", "REFUND_SOURCE_UNSETTLED_FUNDS");

		try {
			reqData.put("sign", WXPayUtils.generateSignature(reqData, payment.getPayAppKey()));
		} catch (Exception e) {
			e.printStackTrace();
			throw new ProRunTimeException("加密参数异常");
		}
		CloseableHttpClient httpclient = null;
		try {
			KeyStore keyStore = KeyStore.getInstance("PKCS12");
			// File flie = new File("D:\\project\\sts\\luckytrip-spring-cloud\\luckytrip-service\\luckytrip-resource-utils\\src\\main\\resources\\wxchatcert1.p12");
			// keyStore.load(new FileInputStream(flie), mchId.toCharArray());
			Resource resource = new ClassPathResource("cert/apiclient_cert.p12");
			keyStore.load(resource.getInputStream(), payment.getPayBossAccount().toCharArray());
			SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(new TrustStrategy() {
				@Override
				public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
					return true;
				}
			}).loadKeyMaterial(keyStore, payment.getPayBossAccount().toCharArray()).build();

			SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null,
					SSLConnectionSocketFactory.getDefaultHostnameVerifier());
			httpclient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
		} catch (Exception e) {
			e.printStackTrace();
			throw new ProRunTimeException("http请求异常:" + PublicMethod.printErrorTrace(e));
		}

		String xml = null;
		try {
			xml = WXPayUtils.mapToXml(reqData);
			HttpPost httpPost = new HttpPost("https://" + WXPayConstants.DOMAIN_API + WXPayConstants.REFUND_URL_SUFFIX);
			httpPost.addHeader("Content-Type", "application/xml");
			// 定义请求实体
			HttpEntity requestEntity = new StringEntity(xml, "UTF-8");
			httpPost.setEntity(requestEntity);
			CloseableHttpResponse response = httpclient.execute(httpPost);
			String result = null;
			if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				HttpEntity resEntity = response.getEntity();
				result = EntityUtils.toString(resEntity, "UTF-8");
			}
			LOG.info("申请退款接口返回信息：{}", result);
			Map<String, String> resultMap = WXPayUtils.xmlToMap(result);
			String returnCode = resultMap.get("return_code");
			String resultCode = resultMap.get("result_code");
			if ("SUCCESS".equals(returnCode) && "SUCCESS".equals(resultCode)) {
				return resultMap;
			} else {
				throw new ProRunTimeException(ErrCode.FAIL.getReturnCode(), result);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw new ProRunTimeException("支付参数转换xml异常或返回异常");
		}
	}
}
